# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Discrete fields (scalars or vectors) descriptions.
* :class:`~hysop.fields.discrete_field.DiscreteScalarFieldViewContainerI`
* :class:`~hysop.fields.discrete_field.DiscreteScalarField`
* :class:`~hysop.fields.discrete_field.DiscreteTensorField`
* :class:`~hysop.fields.discrete_field.DiscreteScalarFieldView`
"""
from abc import ABCMeta, abstractmethod
import numpy as np
from hysop import vprint
from hysop.tools.decorators import debug
from hysop.tools.htypes import check_instance, first_not_None
from hysop.tools.variable import VariableTag, Variable
from hysop.tools.handle import TaggedObject, TaggedObjectView
from hysop.tools.htypes import to_tuple, first_not_None
from hysop.tools.numpywrappers import npw
from hysop.constants import GhostOperation, MemoryOrdering
from hysop.core.arrays.all import ArrayBackend
from hysop.topology.topology import Topology, TopologyView, TopologyState
from hysop.fields.continuous_field import (
Field,
VectorField,
TensorField,
NamedScalarContainerI,
NamedTensorContainerI,
)
from hysop.domain.domain import DomainView
from hysop.mesh.mesh import MeshView
[docs]
class DiscreteScalarFieldViewContainerI(metaclass=ABCMeta):
"""
Common abstract interface for scalar and tensor-like container of
discrete field views.
"""
@debug
def __init__(self, **kwds):
super().__init__(**kwds)
@debug
def __new__(cls, **kwds):
return super().__new__(cls, **kwds)
@property
def is_scalar(self):
return not self.is_tensor
[docs]
@abstractmethod
def discrete_field_views(self):
"""
Return all unique discrete field views contained in this discrete
field view container.
"""
pass
[docs]
def discrete_fields(self):
"""
Return all unique discrete fields contained in this discrete field view container.
"""
return tuple(dfield._dfield for dfield in self.discrete_field_views())
[docs]
def continuous_fields(self):
"""
Return all unique continuous fields contained in this discrete field view container.
"""
return tuple(dfield._dfield._field for dfield in self.discrete_field_views())
@property
def dfields(self):
"""
Alias for self.discrete_field_views().
"""
return self.discrete_field_views()
@property
def nb_components(self):
"""
Total number of components of this discrete field container, exluding None entries,
but including duplicate fields.
"""
return len(self.discrete_field_views())
[docs]
def ids_to_components(self, ids):
"""Convert tensor coordinates into 1d offsets."""
check_instance(ids, tuple, values=(int, tuple), allow_none=True)
return tuple(self.id_to_component(_) for _ in ids)
[docs]
def id_to_component(self, val):
check_instance(val, (int, tuple))
if isinstance(val, int):
return val
elif len(val) == 1:
return val[0]
else:
stride = np.empty(shape=self.shape, dtype=np.int8).strides
assert len(val) == len(stride)
return sum(i * stride for (i, stride) in zip(val, stride))
[docs]
@abstractmethod
def initialize(self, **kwds):
"""Initialize all contained discrete fields."""
pass
[docs]
@abstractmethod
def fill(self, **kwds):
"""Fill all contained discrete field with an initial value."""
pass
[docs]
@abstractmethod
def randomize(self, **kwds):
"""Fill all contained discrete field with random values."""
pass
[docs]
@abstractmethod
def copy(self, from_dfield, **kwds):
"""Fill this discrete field with values from another one."""
pass
[docs]
@abstractmethod
def clone(self, tstate=None):
"""
Create a new temporary DiscreteScalarField container and allocate it
like the current object, with possibly a different topology state.
This should only be used for debugging and testing purpose.
The generated discrete field is not registered to the continuous
field.
"""
pass
[docs]
@abstractmethod
def tmp_dfield_like(self, name, **kwds):
r"""
Create a new Field container and a new temporary CartesianDiscreteField.
like the current object, possibly on a different backend.
/!\ The returned discrete field is not allocated.
"""
pass
[docs]
@abstractmethod
def has_ghosts(self):
"""Return True if any contained discrete field requires ghost exchanges."""
pass
[docs]
@abstractmethod
def build_ghost_exchanger(self, **kwds):
"""
Build a ghost exchanger, possibly on different data.
Usefull for operator apply.
"""
pass
[docs]
@abstractmethod
def exchange_ghosts(self, build_launcher=False, **kwds):
"""
Exchange ghosts using cached ghost exchangers which are built at first use.
ie. Exchange every ghosts components of self.data using current topology state.
By default all ghosts are exchanged.
"""
pass
[docs]
@abstractmethod
def accumulate_ghosts(self, **kwds):
"""
Specialization of ghost exchange for ghost summation.
"""
pass
[docs]
@abstractmethod
def match(self, other, invert=False):
"""Check if two DiscreteScalarFieldViews are equivalent."""
pass
[docs]
@abstractmethod
def view(self, topology_state):
"""
Return a view on this DiscreteScalarField using given topology state.
"""
pass
[docs]
@abstractmethod
def as_any_dfield(self, memory_order, **kwds):
"""
Quickly take a view on this DiscreteScalarFieldView using self topology state
supplemented by a MemoryOrdering.
"""
pass
[docs]
def as_contiguous_dfield(self, **kwds):
"""
Quickly take a view on this DiscreteScalarFieldView using self topology state
supplemented by a MemoryOrdering.C_CONTIGUOUS.
"""
return self.as_any_dfield(memory_order=MemoryOrdering.C_CONTIGUOUS, **kwds)
[docs]
def as_fortran_dfield(self, **kwds):
"""
Quickly take a view on this DiscreteScalarFieldView using self topology state
supplemented by a MemoryOrdering.F_CONTIGUOUS.
"""
return self.as_any_dfield(memory_order=MemoryOrdering.F_CONTIGUOUS, **kwds)
[docs]
@abstractmethod
def integrate(self, **kwds):
"""Sum all the values in the mesh."""
pass
[docs]
@abstractmethod
def short_description(self):
"""Short description of this field as a string."""
pass
[docs]
@abstractmethod
def long_description(self):
"""Long description of this field as a string."""
pass
@abstractmethod
def _get_data(self):
"""Return contained arrays as a tuple."""
pass
@abstractmethod
def _set_data(self, copy_data):
"""Copy data to contained arrays."""
pass
@abstractmethod
def _get_buffers(self):
"""Return all array data as a buffers as a tuple."""
pass
@abstractmethod
def _get_sdata(self):
"""Return contained array."""
pass
@abstractmethod
def _set_sdata(self, copy_data):
"""Copy data to contained array."""
pass
@abstractmethod
def _get_sbuffer(self):
"""Return contained buffer."""
pass
@abstractmethod
def __getitem__(self, key):
pass
[docs]
def get_attributes(self, *attrs):
"""
Return all matching attributes contained in self.discrete_field_views(),
as a tuple.
"""
objects = ()
for dfield in self.discrete_field_views():
obj = dfield
for attr in attrs:
obj = getattr(obj, attr)
objects += (obj,)
return objects
[docs]
def get_attributes_as_tensor(self, *attrs):
"""
Return all matching attributes contained in self.discrete_field_views(),
as a np.ndarray of objects of the same shape.
"""
objects = np.empty(shape=self.shape, dtype=object)
for idx, dfield in self.nd_iter():
obj = dfield
for attr in attrs:
obj = getattr(obj, attr)
objects[idx] = obj
[docs]
def has_unique_attribute(self, *attr):
r"""
Return true if all contained discrete fields share the same attribute
(as stated by the == comparisson operator).
/!\ Can be slow to evaluate /!\
"""
def are_equal(a, b):
if a is b:
return True
if type(a) != type(b):
return False
if isinstance(a, (list, tuple, set, frozenset)):
for ai, bi in zip(a, b):
if not are_equal(ai, bi):
return False
return True
if isinstance(a, dict):
for k in set(a.keys()).union(b.keys()):
if (k not in a) or (k not in b):
return False
ak, bk = a[k], b[k]
if not are_equal(ak, bk):
return False
return True
if isinstance(a, np.ndarray):
return np.array_equal(a, b)
return a == b
objects = self.get_attributes(*attr)
obj0 = objects[0]
for obj in objects[1:]:
if not are_equal(obj0, obj):
return False
return True
[docs]
def has_unique_backend(self):
"""Return true if all contained discrete fields share the same backend."""
return self.has_unique_attribute("backend")
[docs]
def has_unique_backend_kind(self):
"""Return true if all contained discrete fields share the same backend kind."""
return self.has_unique_attribute("backend_kind")
[docs]
def has_unique_domain(self):
"""Return true if all contained discrete fields share the same domain view."""
return self.has_unique_attribute("domain")
[docs]
def has_unique_topology(self):
"""Return true if all contained discrete fields share the same topology view."""
return self.has_unique_attribute("topology")
[docs]
def has_unique_topology_state(self):
"""Return true if all contained discrete fields share the same topology state."""
return self.has_unique_attribute("topology_state")
[docs]
def has_unique_mesh(self):
"""Return true if all contained discrete fields share the same mesh view."""
return self.has_unique_attribute("mesh")
[docs]
def has_unique_dtype(self):
"""Return true if all contained discrete fields share the same data type."""
return self.has_unique_attribute("dtype")
[docs]
def get_unique_attribute(self, *attr):
r"""
Try to return the unique attribute common to all contained discrete fields.
Raise an AttributeError if a attribute is not unique accross contained
discrete field views.
/!\ Can be slow to evaluate due to uniqueness check /!\
"""
if self.has_unique_attribute(*attr):
return self.discrete_field_views()[0].get_attributes(*attr)[0]
msg = "{} is not unique accross contained discrete fields."
msg = msg.format(".".join(attr))
raise AttributeError(msg)
@property
def backend(self):
"""
Try to return the unique backend common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("backend")
@property
def backend_kind(self):
"""
Try to return the unique backend kind common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("backend_kind")
@property
def domain(self):
"""
Try to return the unique topology view common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("domain")
@property
def topology_state(self):
"""
Try to return the unique topology state common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("topology_state")
@property
def topology(self):
"""
Try to return the unique topology view common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("topology")
@property
def mesh(self):
"""
Try to return the unique mesh view common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("mesh")
@property
def dtype(self):
"""
Try to return the unique data type common to all contained discrete fields,
else raise an AttributeError.
"""
return self.get_unique_attribute("dtype")
@property
def ctype(self):
"""Get the data type of the discrete field as a C type (may raise AttributeError)."""
from hysop.backend.device.codegen.base.variables import dtype_to_ctype
dtype = self.dtype
return dtype_to_ctype(dtype)
@property
def dim(self):
"""Get dimension of the shared domain (may raise AttributeError)."""
return self.domain.dim
def __eq__(self, other):
return self.match(other)
def __ne__(self, other):
return self.match(other, invert=True)
def __str__(self):
return self.long_description()
[docs]
class DiscreteScalarFieldView(
DiscreteScalarFieldViewContainerI, TaggedObjectView, VariableTag, metaclass=ABCMeta
):
"""
View over a DiscreteScalarField (taking into account a topology state).
"""
__slots__ = ("_dfield", "_topology_state", "_topology_view", "_symbol")
@property
def is_tensor(self):
return False
@debug
def __init__(self, dfield, topology_state, **kwds):
super().__init__(obj_view=dfield, variable_kind=Variable.DISCRETE_FIELD, **kwds)
@debug
def __new__(cls, dfield, topology_state, **kwds):
check_instance(
dfield, DiscreteScalarField, allow_none=issubclass(cls, DiscreteScalarField)
)
check_instance(topology_state, TopologyState)
obj = super().__new__(
cls, obj_view=dfield, variable_kind=Variable.DISCRETE_FIELD, **kwds
)
dfield = first_not_None(dfield, obj)
obj._dfield = dfield
obj._topology_state = topology_state
obj._topology_view = dfield._topology.view(topology_state)
if issubclass(cls, DiscreteScalarField):
from hysop.symbolic.field import SymbolicDiscreteField
obj._symbol = SymbolicDiscreteField.from_field(obj)
else:
obj._symbol = None
if __debug__:
obj.__check_vars()
return obj
def __check_vars(self):
"""Check properties and types."""
check_instance(self.dtype, np.dtype)
check_instance(self.name, str)
check_instance(self.pretty_name, str)
check_instance(self.dim, int, minval=1)
check_instance(self.topology, TopologyView)
check_instance(self.backend, ArrayBackend)
check_instance(self.domain, DomainView)
check_instance(self.mesh, MeshView)
check_instance(self.topology_state, TopologyState)
check_instance(self.is_read_only, bool)
check_instance(self.kind, Variable)
@property
def ndim(self):
"""Number of dimensions of this this tensor."""
return 0
[docs]
def nd_iter(self):
"""Return an nd-indexed iterator of contained objects."""
yield ((0,), self)
[docs]
def __iter__(self):
"""Return an iterator on unique scalar objects."""
return (self,).__iter__()
[docs]
def __tuple__(self):
"""
Fix hysop.tools/type.to_tuple for FieldContainers,
because __iter__ has been redefined.
"""
return (self,)
[docs]
def __contains__(self, obj):
"""Check if a scalar object is contained in self."""
return obj is self
def __getitem__(self, slc):
return self
[docs]
def discrete_field_views(self):
return (self,)
def _get_field(self):
"""Return the continuous field associated to this discrete field."""
return self._dfield._field
def _get_dfield(self):
"""Get the discrete field on which the view is."""
return self._dfield
def _get_field(self):
"""Get the continuous field on which the view is."""
return self._dfield._field
def _get_topology_state(self):
"""Get the topology state of this view."""
return self._topology_state
def _get_is_read_only(self):
"""Return true if this view is a read-only."""
return self._topology_state.is_read_only
def _get_name(self):
"""Get the name of the discrete field."""
return self._dfield._name
def _get_pretty_name(self):
"""Get the name of the discrete field."""
return self._dfield._pretty_name
def _get_latex_name(self):
"""Get the latex name of the discrete field."""
return self._dfield._latex_name
def _get_var_name(self):
"""Get the latex name of the discrete field."""
return self._dfield._var_name
def _get_dtype(self):
"""Get the data type of the discrete field."""
return self._dfield._field.dtype
def _get_initial_values(self):
"""
Get the default initial values of this field
as a tuple (compute, ghosts).
"""
return self._dfield._field.initial_values
def _get_topology(self):
"""Return a topology view on which this discrete field is defined."""
return self._topology_view
def _get_domain(self):
"""Return a domain view on which this discrete field is defined."""
return self._topology_view._domain_view
def _get_mesh(self):
"""Return a mesh view on which the current process operate for this discrete field."""
return self._topology_view._mesh_view
def _get_backend(self):
"""Get the array backend used to allocate this discrete field data."""
return self._topology_view.backend
def _get_backend_kind(self):
"""Get the array backend kind used to allocate this discrete field data."""
return self._topology_view.backend.kind
[docs]
def match(self, other, invert=False):
"""Check if two DiscreteScalarFieldViews are equivalent."""
if not isinstance(other, DiscreteScalarFieldView):
return NotImplemented
eq = self._dfield._topology is other._dfield._topology
eq &= self._dfield._field is other._dfield._field
eq &= self._dfield._name == other._dfield._name
eq &= self._dfield._pretty_name == other._dfield._pretty_name
eq &= self._topology_state == other._topology_state
if invert:
return not eq
else:
return eq
[docs]
def honor_memory_request(self, *args, **kwds):
self._dfield.honor_memory_request(*args, **kwds)
def _get_memory_request(self):
"""Get memory request that should be allocated for this TmpCartesianDiscreteField."""
return getattr(self._dfield, "_memory_request", None)
def _get_memory_request_id(self):
"""Get memory request id that should be allocated for this TmpCartesianDiscreteField."""
return getattr(self._dfield, "_memory_request_id", None)
def __hash__(self):
h = id(self._dfield._topology)
h ^= id(self._dfield._field)
h ^= hash(self._dfield._name)
h ^= hash(self._dfield._pretty_name)
h ^= hash(self._topology_state)
return h
@property
def symbol(self):
return self._dfield._symbol
@property
def s(self):
return self._dfield._symbol
dfield = property(_get_dfield)
field = property(_get_field)
topology_state = property(_get_topology_state)
is_read_only = property(_get_is_read_only)
name = property(_get_name)
pretty_name = property(_get_pretty_name)
latex_name = property(_get_latex_name)
var_name = property(_get_var_name)
dtype = property(_get_dtype)
initial_values = property(_get_initial_values)
topology = property(_get_topology)
backend = property(_get_backend)
backend_kind = property(_get_backend_kind)
domain = property(_get_domain)
mesh = property(_get_mesh)
memory_request = property(_get_memory_request)
memory_request_id = property(_get_memory_request_id)
[docs]
class DiscreteScalarField(NamedScalarContainerI, TaggedObject, metaclass=ABCMeta):
"""
Discrete representation of scalar or vector fields,
A DiscreteScalarField is distributed set of mesh data (hysop.mesh.mesh.Mesh)
wich are a collection of numpy like multidimensional arrays allocated using a
specific backend (hysop.core.arrays.array.ArrayBackend).
A DiscreteScalarField is the result of discretizing a continuous Field
(hysop.field.continuous_field.Field) defined on a specific domain
(hysop.domain.domain.Domain) distributed accross processes through
a topology (hysop.topology.topology.Topology).
Ghost exchangers are automatically built for all discrete fields.
Ghost exchangers may require additional memory buffers,
depending on the discrete field topology backend and the ghost exchange strategy.
"""
@debug
def __init__(
self,
field,
topology,
register_discrete_field=True,
name=None,
pretty_name=None,
var_name=None,
latex_name=None,
**kwds,
):
super().__init__(
name=name,
pretty_name=pretty_name,
var_name=var_name,
latex_name=latex_name,
tag_prefix="df",
**kwds,
)
[docs]
@debug
def __new__(
cls,
field,
topology,
register_discrete_field=True,
name=None,
pretty_name=None,
var_name=None,
latex_name=None,
**kwds,
):
"""
Creates a discrete field for a given continuous field and topology.
Parameters
----------
field: :class:`~hysop.field.continuous_field.Field`
The continuous field that is dicrerized.
topology: :class:`~hysop.topology.topology.Topology`
The topology where to allocate the discrete field.
register_discrete_field: bool, defaults to True
If set register input topology to input continuous field.
name : string, optional
A name for the field.
pretty_name: str, optional.
A pretty name used for display whenever possible.
Defaults to name.
var_name: string, optional.
A variable name used for code generation.
This will be passed to the symbolic representation of this discrete field.
latex_name: string, optional.
A variable name used for latex generation.
This will be passed to the symbolic representation of this discrete field.
kwds: dict
Base class arguments.
"""
check_instance(field, Field)
check_instance(topology, Topology)
check_instance(name, str, allow_none=True)
check_instance(pretty_name, str, allow_none=True)
_name, _pretty_name, _var_name, _latex_name = cls.format_discrete_names(
field.name, field.pretty_name, field.var_name, field.latex_name, topology
)
pretty_name = first_not_None(pretty_name, name, _pretty_name)
var_name = first_not_None(var_name, name, _var_name)
latex_name = first_not_None(latex_name, name, _latex_name)
name = first_not_None(name, _name)
obj = super().__new__(
cls,
name=name,
pretty_name=pretty_name,
var_name=var_name,
latex_name=latex_name,
tag_prefix="df",
**kwds,
)
assert isinstance(
obj, DiscreteScalarFieldView
), "DiscreteScalarFieldView not inherited."
if field._domain is not topology._domain:
msg = "Field domain {} and topology domain {} do not match."
msg = msg.format(field.domain.full_tag, topology.domain.full_tag)
raise ValueError(msg)
if register_discrete_field:
if topology in field.discrete_fields:
msg = "Field {} was already discretized on topology {}.".format(
field.name, topology.tag
)
raise RuntimeError(msg)
field.discrete_fields[topology] = obj
obj._topology = topology
obj._field = field
obj._clone_id = 0
obj._ghost_exchangers = {}
return obj
[docs]
class DiscreteTensorField(
NamedTensorContainerI, DiscreteScalarFieldViewContainerI, TaggedObject
):
"""
A tensor discrete field is a collection of scalar discrete fields views.
This object handles a numpy.ndarray of discrete scalar field views,
which may have different attributes (different data types for
example) and different topology states.
A tensor field garanties that each different field objects have
unique names and pretty names within the tensor field. A given
continuous scalar discrete field may appear at multiple indices with different
views.
Some components may also be set to None.
Is also garanties that all fields shares the same domain, but contained
discrete fields may be defined on different topologies.
"""
@property
def is_tensor(self):
return True
def __init__(
self, field, dfields, name=None, pretty_name=None, latex_name=None, **kwds
):
super().__init__(
name=name,
pretty_name=pretty_name,
latex_name=latex_name,
tag_prefix="tdf",
tagged_cls=DiscreteTensorField,
contained_objects=dfields,
**kwds,
)
def __new__(
cls, field, dfields, name=None, pretty_name=None, latex_name=None, **kwds
):
check_instance(field, TensorField)
check_instance(
dfields, npw.ndarray, dtype=object, values=DiscreteScalarFieldView
)
assert npw.array_equal(field.shape, dfields.shape)
tensor_cls = cls.determine_tensor_cls(dfields)
if tensor_cls is not cls:
return tensor_cls.__new__(
tensor_cls,
field=field,
dfields=dfields,
name=name,
pretty_name=pretty_name,
**kwds,
)
_name, _pretty_name, _, _latex_name = DiscreteScalarField.format_discrete_names(
field.name, field.pretty_name, None, field.latex_name, None
)
name = first_not_None(name, _name)
pretty_name = first_not_None(pretty_name, _pretty_name)
latex_name = first_not_None(latex_name, _latex_name)
obj = super().__new__(
cls,
name=name,
pretty_name=pretty_name,
latex_name=latex_name,
tag_prefix="tdf",
tagged_cls=DiscreteTensorField,
contained_objects=dfields,
**kwds,
)
obj._field = field
obj._dfields = dfields
obj._clone_id = 0
from hysop.symbolic.field import SymbolicDiscreteFieldTensor
obj._symbol = SymbolicDiscreteFieldTensor(dfield=obj)
obj._check_names()
return obj
[docs]
@classmethod
def determine_tensor_cls(cls, dfields):
"""Determine the Tensor container best suited for contained dfields."""
from hysop.fields.cartesian_discrete_field import (
CartesianDiscreteScalarFieldView,
CartesianDiscreteTensorField,
)
if isinstance(dfields, npw.ndarray):
dfields = tuple(dfields.ravel().tolist())
check_instance(dfields, tuple, values=DiscreteScalarFieldView)
if all(
isinstance(dfield, CartesianDiscreteScalarFieldView) for dfield in dfields
):
return CartesianDiscreteTensorField
else:
return cls
[docs]
@classmethod
def from_dfields(cls, name, dfields, shape, pretty_name=None, **kwds):
"""
Create a TensorField and a DiscreteTensorField
from a list of discrete fields and a shape.
"""
dfields = to_tuple(dfields)
shape = to_tuple(shape)
check_instance(dfields, tuple, values=(DiscreteScalarFieldView,), minsize=1)
check_instance(shape, tuple, values=int)
cls._check_dfields(*dfields)
fields = tuple(dfield._dfield._field for dfield in dfields)
field = TensorField.from_fields(
name=name, pretty_name=pretty_name, fields=fields, shape=shape
)
dfields = npw.asarray(dfields, dtype=object).reshape(shape)
return cls(field=field, dfields=dfields, **kwds)
[docs]
@classmethod
def from_dfield_array(cls, name, dfields, pretty_name=None, **kwds):
"""
Create a TensorField and a DiscreteTensorField from np.ndarray of discrete fields.
"""
check_instance(name, str)
check_instance(pretty_name, str, allow_none=True)
check_instance(
dfields, npw.ndarray, dtype=object, values=DiscreteScalarFieldView
)
shape = dfields.shape
dfields = tuple(dfields.ravel().tolist())
return cls.from_dfields(
dfields=dfields, shape=shape, name=name, pretty_name=pretty_name, **kwds
)
@classmethod
def _check_dfields(cls, *dfields):
"""Check that at least one field is specified."""
dfield0 = first_not_None(*dfields)
if dfield0 is None:
msg = "Tensor discrete field {} should at least contain a valid DiscreteScalarField."
msg = msg.format(dfield0.name)
raise ValueError(msg)
def _check_names(self):
"""Check that discrete fields names are unique."""
names = {}
pnames = {}
for dfield in self:
name = dfield.name
pname = dfield.pretty_name
if (name in names) and (names[name] != dfield):
msg = "Name {} was already used by another discrete field."
msg = msg.format(name)
raise ValueError(msg)
if (pname in pnames) and (pnames[pname] != dfield):
msg = "Name {} was already used by another discrete field."
msg = msg.format(pname)
raise ValueError(msg)
names[name] = dfield
pnames[name] = dfield
[docs]
def discrete_field_views(self):
"""
Return all unique discrete field views contained in this discrete
field view container.
"""
ordered_dfields = self._dfields.ravel().tolist()
dfields = set(ordered_dfields)
dfields.discard(None)
# keep field ordering unlike using a set
unique_dfields = ()
for dfield in ordered_dfields:
if (dfield in dfields) and (dfield not in unique_dfields):
unique_dfields += (dfield,)
return unique_dfields
[docs]
def fill(self, *args, **kwds):
"""Fill all contained discrete field with an initial value."""
for idx, dfield in self.nd_iter():
dfield.fill(*args, **kwds)
return self
[docs]
def randomize(self, *args, **kwds):
"""Fill all contained discrete field with random values."""
for idx, dfield in self.nd_iter():
dfield.randomize(*args, **kwds)
return self
[docs]
def copy(self, from_dfield, **kwds):
"""Fill this discrete field with values from another one."""
if isinstance(from_dfield, tuple):
assert len(from_dfield) == self.nb_components
for dfv, src in zip(self.discrete_field_views(), from_dfield):
dfv.copy(from_dfield=src)
else:
check_instance(from_dfield, DiscreteTensorField)
assert npw.array_equal(from_dfield.shape, self.shape)
for idx, dfield in from_dfield.nd_iter():
self[idx].copy(from_dfield=dfield, **kwds)
return self
[docs]
def clone(self, tstate=None, name=None, pretty_name=None, **kwds):
"""
Create a new temporary DiscreteScalarField container and allocate it
like the current object, with possibly a different topology state.
This should only be used for debugging and testing purpose.
The generated discrete field is not registered to the continuous
field.
"""
name = first_not_None(name, f"{self.name}__{self._clone_id}")
pretty_name = first_not_None(
pretty_name, "{}__{}".format(self.pretty_name, self._clone_id)
)
dfields = npw.empty(shape=self.shape, dtype=object)
for idx, dfield in self.nd_iter():
dfields[idx] = dfield.clone(tstate=tstate, **kwds)
self._clone_id += 1
return self.from_dfield_array(
name=name, pretty_name=pretty_name, dfields=dfields, **kwds
)
[docs]
def tmp_dfield_like(self, name, pretty_name=None, **kwds):
r"""
Create a new Field container and a new temporary CartesianDiscreteField.
like the current object, possibly on a different backend.
/!\ The returned discrete field is not allocated.
"""
from hysop.core.memory.memory_request import OperatorMemoryRequests
pretty_name = first_not_None(pretty_name, name)
requests = OperatorMemoryRequests(name + "*")
dfields = npw.empty(shape=self.shape, dtype=object)
for idx, dfield in self.nd_iter():
_name = TensorField.default_name_formatter(basename=name, idx=idx)
_pretty_name = TensorField.default_pretty_name_formatter(
basename=pretty_name, idx=idx
)
_name, _pretty_name, _var_name, _latex_name = (
DiscreteScalarField.format_discrete_names(
_name, _pretty_name, _name, self.latex_name, dfield.topology
)
)
(dfield, request, request_id) = dfield.tmp_dfield_like(
name=_name, pretty_name=_pretty_name, **kwds
)
requests.push_mem_request(request_id, request)
dfields[idx] = dfield
dfield = self.from_dfield_array(
name=name, pretty_name=pretty_name, dfields=dfields
)
return (dfield, requests)
[docs]
def honor_memory_request(self, work, op=None):
"""Honour memory requests for contained temporary discrete fields."""
op = first_not_None(op, self.name)
for idx, dfield in self.nd_iter():
if dfield.is_tmp:
assert hasattr(dfield, "_memory_request_id")
dfield.honor_memory_request(work, op=op)
[docs]
def has_ghosts(self):
"""Return True if any contained discrete field requires ghost exchanges."""
return any(dfield.has_ghosts() for dfield in self.discrete_field_views())
[docs]
def match(self, other, invert=False):
"""Check if two DiscreteScalarFieldViews container are equivalent."""
check_instance(other, DiscreteTensorField)
assert npw.array_equal(self.shape, other.shape)
eq = all(
dfield.match(other[idx], invert=False) for (idx, dfield) in self.nd_iter()
)
eq &= npw.array_equal(self.shape, other.shape)
eq &= self._name == other._name
eq &= self._pretty_name == other._pretty_name
if invert:
return not eq
else:
return eq
def __hash__(self):
h = hash(self._name)
h ^= hash(self._pretty_name)
h ^= hash(self.shape)
for _, dfield in self.nd_iter():
h ^= hash(dfield)
return h
[docs]
def view(self, topology_state, name, pretty_name=None):
"""
Return a view on contained DiscreteFields using given topology state.
"""
dfields = npw.empty(shape=self.shape, dtype=object)
for idx, dfield in self.nd_iter():
dfields[idx] = dfield.view(topology_state=topology_state)
dfield = self.from_dfield_array(
name=name, pretty_name=pretty_name, dfields=dfields
)
return dfield
[docs]
def as_any_dfield(self, memory_order, name=None, pretty_name=None, **kwds):
"""
Quickly take a view on this DiscreteScalarFieldView using self topology state
supplemented by a MemoryOrdering.
"""
assert memory_order in (
MemoryOrdering.C_CONTIGUOUS,
MemoryOrdering.F_CONTIGUOUS,
)
suffix = "_" + "F" if (memory_order is MemoryOrdering.F_CONTIGUOUS) else "C"
name = first_not_None(name, self.name + suffix)
pretty_name = first_not_None(pretty_name, self.pretty_name + suffix)
dfields = npw.empty(shape=self.shape, dtype=object)
for idx, dfield in self.nd_iter():
state = dfield.topology_state
dfields[idx] = dfield.as_any_dfield(memory_order=memory_order, **kwds)
dfield = self.from_dfield_array(
name=name, pretty_name=pretty_name, dfields=dfields
)
return dfield
[docs]
def as_contiguous_dfield(self, **kwds):
"""
Quickly take a view on this DiscreteScalarFieldView using self topology state
supplemented by a MemoryOrdering.C_CONTIGUOUS.
"""
return self.as_any_dfield(memory_order=MemoryOrdering.C_CONTIGUOUS, **kwds)
[docs]
def as_fortran_dfield(self, **kwds):
"""
Quickly take a view on this DiscreteScalarFieldView using self topology state
supplemented by a MemoryOrdering.F_CONTIGUOUS.
"""
return self.as_any_dfield(memory_order=MemoryOrdering.F_CONTIGUOUS, **kwds)
[docs]
def short_description(self):
"""Short description of this discrete field container."""
s = "{}[name={}, pname={}, shape={}, nb_components={}]"
s = s.format(
self.full_tag, self.name, self.pretty_name, self.shape, self.nb_components
)
return s
[docs]
def long_description(self):
"""Long description of this discrete field container."""
s = """\
{}
*name: {}
*pname: {}
*shape: {}
*nb_components: {}
*symbolic repr.:
""".format(
self.full_tag, self.name, self.pretty_name, self.shape, self.nb_components
)
s += " " + "\n ".join(str(self.symbol).split("\n"))
return s
[docs]
def common_dtype(self):
"""Return common data type of contained fields."""
if self.has_unique_dtype:
dtype = self.dtype
else:
dtype = object
return dtype
[docs]
def integrate(self, scale=True, dtype=None, **kwds):
"""Compute volume integrals by suming values scaled by elementary volume dx."""
dtype = self.common_dtype()
integrals = npw.zeros(shape=self.shape, dtype=dtype)
for idx, dfield in self.nd_iter():
integrals[idx] = dfield.integrate(scale=scale, **kwds).tolist()[0]
return integrals
[docs]
def exchange_ghosts(
self, build_exchanger=False, build_launcher=False, evt=None, **kwds
):
"""
Exchange ghosts using cached ghost exchangers which are built at first use.
ie. Exchange every ghosts components of self.data using current topology state.
By default all ghosts are exchanged.
"""
if build_launcher or build_exchanger:
assert evt is None, "Cannot spevify event while building a launcher."
from hysop.fields.ghost_exchangers import MultiGhostExchanger
ghost_exchangers = MultiGhostExchanger(name=f"{self.name}_ghost_exchange")
all_none = True
for idx, dfield in self.nd_iter():
ge = dfield.exchange_ghosts(
build_launcher=False, build_exchanger=True, **kwds
)
all_none &= ge is None
ghost_exchangers += ge
if all_none:
return None
elif build_exchanger:
return ghost_exchangers
else:
return ghost_exchangers._build_launcher()
else:
evt = None
for idx, dfield in self.nd_iter():
_evt = dfield.exchange_ghosts(**kwds)
evt = first_not_None(_evt, evt)
return evt
[docs]
def accumulate_ghosts(
self, build_launcher=False, build_exchanger=False, evt=None, **kwds
):
"""
Exchange ghosts using cached ghost exchangers which are built at first use.
ie. Exchange every ghosts components of self.data using current topology state.
Specialization for ghost summation.
"""
if build_launcher or build_exchanger:
assert evt is None, "Cannot spevify event while building a launcher."
from hysop.fields.ghost_exchangers import MultiGhostExchanger
ghost_exchangers = MultiGhostExchanger(name=f"{self.name}_ghost_exchange")
all_none = True
for idx, dfield in self.nd_iter():
ge = dfield.accumulate_ghosts(
build_launcher=False, build_exchanger=True, **kwds
)
all_none &= ge is None
ghost_exchangers += ge
if all_none:
return None
elif build_exchanger:
return ghost_exchangers
else:
return ghost_exchangers._build_launcher()
else:
evt = None
for idx, dfield in self.nd_iter():
_evt = dfield.accumulate_ghosts(**kwds)
evt = first_not_None(_evt, evt)
return evt
[docs]
def build_ghost_exchanger(self, **kwds):
"""
Build a ghost exchanger, possibly on different data.
Usefull for operator apply.
"""
from hysop.fields.ghost_exchangers import MultiGhostExchanger
ghost_exchangers = MultiGhostExchanger(name=f"{self.name}_ghost_exchange")
all_none = True
for idx, dfield in self.nd_iter():
ge = dfield.build_ghost_exchanger(**kwds)
all_none &= ge is None
ghost_exchangers += ge
if all_none:
return None
else:
return ghost_exchangers
def __getitem__(self, slc):
dfields = self._dfields.__getitem__(slc)
if isinstance(dfields, DiscreteScalarFieldView):
return dfields
else:
name = f"{self._field.name}_view"
pretty_name = f"{self._field.pretty_name}_view"
return self.from_dfield_array(
name=name, pretty_name=pretty_name, dfields=dfields
)
def _get_data(self):
return tuple(dfield.sdata for dfield in self.discrete_field_views())
def _set_data(self, copy_data):
dfields = self.discrete_field_views()
if (len(dfields) == 1) and isinstance(copy_data, npw.ndarray):
copy_data = (copy_data,)
check_instance(copy_data, tuple, size=len(dfields))
for dfield, data in zip(dfields, copy_data):
dfield._set_data(data)
def _get_buffers(self):
return tuple(dfield.sbuffer for dfield in self.discrete_field_views())
def _get_sdata(self):
self._raise_sdata()
def _set_sdata(self, copy_data):
self._raise_sdata()
def _get_sbuffer(self):
self._raise_sdata()
def _raise_sdata(self):
msg = "sdata and sbuffer are only attributes of ScalarFields, "
msg += "use data or buffers instead."
raise RuntimeError(msg)
data = property(_get_data, _set_data)
buffers = property(_get_buffers)
sdata = property(_get_sdata, _set_sdata)
sbuffer = property(_get_sbuffer)
[docs]
def initialize(self, *args, **kwds):
msg = "This method is only defined for specific topologies "
msg += "(see CartesianDiscreteTensorField)."
raise RuntimeError(msg)
[docs]
def norm(self, *args, **kwds):
msg = "This method is only defined for specific topologies "
msg += "(see CartesianDiscreteTensorField)."
raise RuntimeError(msg)
[docs]
def distance(self, *args, **kwds):
msg = "This method is only defined for specific topologies "
msg += "(see CartesianDiscreteTensorField)."
raise RuntimeError(msg)
DiscreteField = (DiscreteScalarField, DiscreteTensorField)
"""A DiscreteField is either of DiscreteScalarField or a DiscreteTensorField"""